package com.ruoyi.framework.aspectj;

import java.lang.reflect.Method;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

import com.ruoyi.common.utils.ServletUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Component;
import com.ruoyi.common.annotation.RateLimiter;
import com.ruoyi.common.enums.LimitType;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.ip.IpUtils;

/**
 * 限流处理
 *
 * @author ruoyi
 */
@Aspect
@Component
public class RateLimiterAspect
{
    private static final Logger log = LoggerFactory.getLogger(RateLimiterAspect.class);

    private ConcurrentMap<String, TokenBucket> buckets = new ConcurrentHashMap<>();

    @Before("@annotation(rateLimiter)")
    public void doBefore(JoinPoint point, RateLimiter rateLimiter) throws Throwable
    {
        String key = rateLimiter.key();
        int time = rateLimiter.time();
        int count = rateLimiter.count();

        String combineKey = getCombineKey(rateLimiter, point);
        TokenBucket bucket = buckets.computeIfAbsent(combineKey, k -> new TokenBucket(count, time));

        if (!bucket.tryConsume()) {
            throw new ServiceException("访问过于频繁，请稍候再试");
        }

        log.info("限制请求'{}',当前请求数'{}',缓存key'{}'", count, bucket.getTokens(), key);
    }

    public String getCombineKey(RateLimiter rateLimiter, JoinPoint point)
    {
        StringBuilder stringBuilder = new StringBuilder(rateLimiter.key());
        if (rateLimiter.limitType() == LimitType.IP) {
            stringBuilder.append(IpUtils.getIpAddr(ServletUtils.getRequest())).append("-");
        }
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();
        Class<?> targetClass = method.getDeclaringClass();
        stringBuilder.append(targetClass.getName()).append("-").append(method.getName());
        return stringBuilder.toString();
    }

    private static class TokenBucket {
        private final int capacity;
        private long lastRefillTime;
        private double tokens;

        TokenBucket(int capacity, long refillInterval) {
            this.capacity = capacity;
            this.tokens = capacity;
            this.lastRefillTime = System.currentTimeMillis();
            // Start a background thread to refill the bucket periodically
            Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate(
                    () -> {
                        synchronized (TokenBucket.this) {
                            long now = System.currentTimeMillis();
                            double elapsedSeconds = (now - lastRefillTime) / 1000.0;
                            tokens = Math.min(capacity, tokens + elapsedSeconds * capacity / refillInterval);
                            lastRefillTime = now;
                        }
                    },
                    refillInterval,
                    refillInterval,
                    TimeUnit.MILLISECONDS
            );
        }

        synchronized boolean tryConsume() {
            if (tokens > 0) {
                tokens--;
                return true;
            } else {
                return false;
            }
        }

        synchronized int getTokens() {
            return (int) tokens;
        }
    }
}
